/*
 * Copyright 2014 Igor Morais
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package hollowsoft.slidingdrawer;

import hollowsoft.slidingdrawer.utl.LogUtil;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.*;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;

/**
 * <p> SlidingDrawer hides content out of the screen and allows the user to drag a handle
 * to bring the content on screen. SlidingDrawer can be used vertically or horizontally. </p>
 *
 * <p> A special widget composed of two children views: the handle, that the users drags,
 * and the content, attached to the handle and dragged with it. </p>
 *
 * <p> SlidingDrawer should be used as an overlay inside layouts. This means SlidingDrawer
 * should only be used inside of a FrameLayout or a RelativeLayout for instance. </p>
 *
 * <p> The size of the SlidingDrawer defines how much space the content will occupy once slid
 * out so SlidingDrawer should usually use match_parent for both its dimensions. </p>
 *
 * <p> Inside an XML layout, SlidingDrawer must define the id of the handle and of the
 * content: </p>
 *
 * @author Igor Morais
 */
public class SlidingDrawer extends DirectionalLayout implements Component.DrawTask {
    boolean finishedInflating = false;

    float top = 1900;
    float start = 0;
    float _Delta = 0;
    float begening = 0;
    float ending = 0;
    int scrollState = 0;

    private boolean animating = false;
    private boolean locked = false;

    /**
     * Styleable.
     */
    private boolean expanded = false;

    private Component viewHandle;
    private Component viewContent;

    private OnDrawerOpenListener onDrawerOpenListener;
    private OnDrawerCloseListener onDrawerCloseListener;
    private OnDrawerScrollListener onDrawerScrollListener;

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
     *
     * @param context The applications environment.
     */
    public SlidingDrawer(final Context context) {
        super(context);
        setOrientation(DirectionalLayout.HORIZONTAL);
        setClickable(true);
        addDrawTask(this);
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
     *
     * @param context      The applications environment.
     * @param attributeSet The AttrSet.
     */
    public SlidingDrawer(final Context context, final AttrSet attributeSet) {
        super(context, attributeSet);
        setClickable(true);
        addDrawTask(this);
    }

    /**
     * set the handle
     *
     * @param handle component
     */
    public void setHandle(Component handle) {
        addComponent(handle, 0);
    }

    /**
     * Set the holder
     *
     * @param holder AttrSet
     */
    public void setHolder(Component holder) {
        addComponent(holder);
    }

    /**
     * onFinishInflate
     */
    protected void onFinishInflate() {
        viewHandle = getComponentAt(0);
        viewContent = getComponentAt(1);
        if (viewHandle != null && viewContent != null) {
            Point size = new Point();
            DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getSize(size);
            DirectionalLayout.LayoutConfig layoutConfig;
            if (getOrientation() == VERTICAL) {
                layoutConfig = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
                        ComponentContainer.LayoutConfig.MATCH_CONTENT);
                top = ((Component) getComponentParent()).getHeight() - viewHandle.getHeight();
                setContentPositionY(top);
            } else {
                layoutConfig = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT,
                        ComponentContainer.LayoutConfig.MATCH_PARENT);

                Component component = getComponentAt(0);
                removeComponentAt(0);
                addComponent(component);
                setContentPositionX(-viewContent.getWidth());
            }
            setLayoutConfig(layoutConfig);
            viewHandle.setDraggedListener(1, mDragListener);
            viewContent.setDraggedListener(1, mDragListener);
            finishedInflating = true;
        }
    }

    /**
     * onDraw Method
     *
     * @param component to draw
     * @param canvas    to draw
     */
    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (!finishedInflating) {
            onFinishInflate();
        }
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes open.
     *
     * @param onDrawerOpenListener onDrawerOpenListener The listener to be notified when the drawer is opened.
     */
    public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener) {
        this.onDrawerOpenListener = onDrawerOpenListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes close.
     *
     * @param onDrawerCloseListener onDrawerCloseListener The listener to be notified when the drawer is closed.
     */
    public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener) {
        this.onDrawerCloseListener = onDrawerCloseListener;
    }

    /**
     * <p> Sets the listener that receives a notification when the drawer starts or ends a scroll. </p>
     * <p> A fling is considered as a scroll. A fling will also trigger a drawer opened or drawer closed event. </p>
     *
     * @param onDrawerScrollListener onDrawerScrollListener The listener to be notified when scrolling starts or stops.
     */
    public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener) {
        this.onDrawerScrollListener = onDrawerScrollListener;
    }

    /**
     * Animate with given postion and int value
     *
     * @param position in float
     * @param time     int
     */
    public void animate(final float position, int time) {
        if (!animating) {
            AnimatorProperty anim = createAnimatorProperty();
            if (getOrientation() == VERTICAL) {
                anim.moveByY(position);
            } else {
                anim.moveToX(position);
            }
            anim.setDuration(time).setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                    animating = true;
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                    animating = false;
                    if (getOrientation() == VERTICAL) {
                        if (position > 0) {
                            if (onDrawerCloseListener != null) {
                                onDrawerCloseListener.onDrawerClosed();
                            }
                            expanded = false;
                        } else {
                            if (onDrawerOpenListener != null) {
                                onDrawerOpenListener.onDrawerOpened();
                            }
                            expanded = true;
                        }
                    } else {
                        if (position == 0) {
                            if (onDrawerOpenListener != null) {
                                onDrawerOpenListener.onDrawerOpened();
                            }
                            expanded = true;
                        } else {
                            if (onDrawerCloseListener != null) {
                                onDrawerCloseListener.onDrawerClosed();
                            }
                            expanded = false;
                        }
                    }
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            }).start();
        }
    }


    /**
     * Indicates whether the drawer is scrolling or flinging.
     *
     * @return True if the drawer is scroller or flinging, false otherwise.
     */
    public final boolean isMoving() {
        return (scrollState == 1) || animating;
    }

    /**
     * Indicates whether the drawer is currently fully opened.
     *
     * @return True if the drawer is opened, false otherwise.
     */
    public final boolean isOpened() {
        return expanded;
    }

    /**
     * Locks the SlidingDrawer so that touch events are ignores.
     *
     * @see #unlock()
     */
    public final void scrollLock() {
        locked = true;
    }

    /**
     * Unlocks the SlidingDrawer so that touch events are processed.
     *
     * @see #lock()
     */
    public final void scrollUnlock() {
        locked = false;
    }

    /**
     * Return the animation satus
     *
     * @return animating
     */
    public boolean isAnimating() {
        return animating;
    }

    /**
     * open thr drawer
     */
    public void open() {
        updateBounds();
        if (!isOpened()) {
            if (getOrientation() == VERTICAL) {
                setContentPositionY(ending);
            } else {
                setContentPositionX(0);
            }
            expanded = true;
        }
    }

    /**
     * close the drawer
     */
    public void close() {
        updateBounds();
        if (isOpened()) {
            if (getOrientation() == VERTICAL) {
                setContentPositionY(begening);
            } else {
                setContentPositionX(-viewContent.getWidth());
            }
            expanded = false;
        }
    }

    /**
     * open the drawer with animation
     */
    public void animateOpen() {
        updateBounds();
        if (!isOpened()) {
            if (getOrientation() == VERTICAL) {
                animate(ending - begening, 100);
            } else {
                animate(0, 100);
            }
        }
    }

    /**
     * close the drawer with animation
     */
    public void animateClose() {
        updateBounds();
        if (isOpened()) {
            if (getOrientation() == VERTICAL) {
                animate(begening - getCurrentPosition(), 100);
            } else {
                animate(-viewContent.getWidth(), 100);
            }
        }
    }

    /**
     * toggle
     */
    public void toggle() {
        updateBounds();

        if (isOpened()) {
            animateClose();
        } else {
            animateOpen();
        }
    }

    /**
     * Get current posstion
     *
     * @return getContentPositionX
     */
    public float getCurrentPosition() {
        if (getOrientation() == VERTICAL) {
            return getContentPositionY();
        } else {
            return getContentPositionX();
        }
    }

    /**
     * Update the bounds
     */
    public void updateBounds() {
        if (getOrientation() == VERTICAL) {
            if (begening == 0) {
                begening = getContentPositionY();
            }
            ending = ((Component) getComponentParent()).getHeight() - getHeight();
        } else {
            if (begening == 0) {
                begening = viewHandle.getWidth();
            }
            ending = getWidth();
        }
    }

    Component.DraggedListener mDragListener = new Component.DraggedListener() {
        @Override
        public void onDragDown(Component component, DragInfo dragInfo) {
        }

        /**
         * when Drag start
         * @param component type
         * @param dragInfo info
         */
        @Override
        public void onDragStart(Component component, DragInfo dragInfo) {
            start = ((getOrientation() == VERTICAL) ?
                    dragInfo.startPoint.position[1] : dragInfo.startPoint.position[0]);
            updateBounds();
        }

        /**
         * On drag update
         * @param component type
         * @param dragInfo info
         */
        @Override
        public void onDragUpdate(Component component, DragInfo dragInfo) {
            if (!locked) {
                if (scrollState == 0) {
                    if (onDrawerScrollListener != null) {
                        onDrawerScrollListener.onScrollStarted();
                    }
                    scrollState = 1;
                }
                float delta = (start - ((getOrientation() == VERTICAL)
                        ? dragInfo.updatePoint.position[1] : dragInfo.updatePoint.position[0]));

                if (getOrientation() == VERTICAL) {
                    if (getCurrentPosition() <= ending && delta > 0) {
                        delta = 0;
                    }
                    if (getCurrentPosition() >= begening && delta < 0) {
                        delta = 0;
                    }
                    if ((getCurrentPosition() > ending) && ((getCurrentPosition() - delta) < ending)) {
                        delta = -(ending - getCurrentPosition());
                    } else if ((getCurrentPosition() < begening) && ((getCurrentPosition() + delta) > begening)) {
                        delta = (begening - getCurrentPosition());
                    }
                    createAnimatorProperty().moveByY(-delta).setDuration(0).start();
                } else {
                    if (delta > 0 && getCurrentPosition() <= (-viewContent.getWidth())) {
                        delta = 0;
                    }
                    if (delta < 0 && getCurrentPosition() >= 0) {
                        delta = 0;
                    }
                    if (getCurrentPosition() > -viewContent.getWidth() && (getCurrentPosition() - delta)
                            < -viewContent.getWidth()) {
                        delta = viewContent.getWidth() + getCurrentPosition();
                    }
                    if (getCurrentPosition() < 0 && (getCurrentPosition() - delta) > 0) {
                        delta = getCurrentPosition();
                    }
                    createAnimatorProperty().moveByX(-delta).setDuration(0).start();
                }
                _Delta = delta;
            }
        }

        /**
         * On drag end
         * @param component type
         * @param dragInfo info
         */
        @Override
        public void onDragEnd(Component component, DragInfo dragInfo) {
            if (getOrientation() == VERTICAL) {
                if (_Delta < 0) {
                    animate(begening - getCurrentPosition(), 0);
                } else {
                    animate(ending - getCurrentPosition(), 0);
                }
            } else {
                if (_Delta > 0) {
                    animate(-(viewContent.getWidth()), 0);
                } else if (_Delta < 0) {
                    animate(0, 0);
                }
            }
            if (onDrawerScrollListener != null) {
                scrollState = 0;
                onDrawerScrollListener.onScrollEnded();
            }
        }

        /**
         * when cancel the drag
         * @param component type
         * @param dragInfo info
         */
        @Override
        public void onDragCancel(Component component, DragInfo dragInfo) {
        }
    };
}
